<?php
/**
* Read directory structure
*
* Simple class for directory structure reading.
*
* @copyright Copyright (c) 2012 Maxim Bondarev
* @license http://www.opensource.org/licenses/mit-license.php
* @author Maxim Bondarev
* @version 1.0
*/
class DirectoryReader
{
    const DR_FILE = 1;
    const DR_DIRECTORY = 2;
    
    /**
     * 
     * keys:
     * (string)permissions - file permissions in -rwxrwxrwx or octal format
     * (array)owner        - file/dir owner array with keys: owner,group 
     * (string)name        - file/dir name, short or full path
     * (int)type           - item type self::DR_FILE or self::DR_DIRECTORY
     * (int)mtime          - last modify time
     * (int)size           - file size in bytes
     *
     * @var array storage array for files and dirs
     **/
    public $files;

    /**
     * @var string current directory
     **/
    private $_currentDirectory;
    
    /**
     * @var integer total files size in bytes
     **/
    private $_directorySize;
    
    /**
     * @var string parent directory if avaliable, or NULL
     **/
    private $_parentDirectory;
    
    /**
     * 
     * keys:
     * (bool)get_permissions            - get file permissions?
     * (bool)get_owner                  - get file owner?
     * (bool)store_full_path            - store full path to item?
     * (bool)get_mtime                  - get last modified time of a file? 
     * (bool)get_filesize               - read file size?
     * (bool)get_octal_permissions      - get permissions formatted as 0777? FALSE to get human readable format.
     * (bool)total_files_size           - calculate total files size?
     * (array)exclude_names             - exclude files with this patterns
     *
     * @var array configuration array
     */
    private $_config;
    
    /**
     * @param string directory to read
     * @param array config array [optional]
     */
    function __construct( $dir, $_config = NULL ) 
    {
        $this->readDirectory( $dir, $_config );
    }
    
    /**
     * Read directory structure
     * @param string directory to read
     * @param array config array [optional]
     * @throws Exception
     */
    public function readDirectory( $dir, $_config = NULL )
    {
        if ( NULL === $_config ) {
            $_config = $this->getDefaultConfig();
        } else {
            $_config = array_merge( $this->getDefaultConfig(), $_config );
        }                           
        $this->_currentDirectory = $dir;
        $this->_directorySize = 0;
        $this->files = NULL;
        
        $this->_parentDirectory = $this->findParentDirectory( $dir );

        $counter = 0;

        if ( is_dir( $dir ) ) {
            
            @$d = opendir( $dir ); 
            
            if ( ! $d ) {
                throw new Exception( 'Can\'t open dir '.$dir );
            }
            
            while ( FALSE !== ( $currentItem=readdir( $d ) ) ) {
                
                // Skip this item?
                if ( ! empty ( $_config['exclude_names'] )  && 
                TRUE === $this->checkName ( $currentItem, $_config['exclude_names'] ) )
                    continue;
                
                // Read file permissions ?
                $this->files[$counter]['permissions'] =                         
                    ( TRUE === $_config['get_permissions'] ) ? 
                        $this->getFilePermissions( "$dir/$currentItem", 
                        $_config['get_octal_permissions'] ) : NULL;
                
                // Read file owner ?                                
                $this->files[$counter]['owner'] = 
                    ( TRUE === $_config['get_owner'] ) ?
                        $this->getFileOwner( "$dir/$currentItem" ) : NULL;
                
                // Short or full path ?     
                $this->files[$counter]['name'] = 
                    ( TRUE === $_config['store_full_path'] ) ?
                    realpath( "$dir/$currentItem" ) : $currentItem ;
                
                if( is_file( "$dir/$currentItem" ) ) {          
                            
                    $this->files[$counter]['type'] = self::DR_FILE;                     
                                                                                            
                    // Read time of file?
                    $this->files[$counter]['mtime'] = 
                        ( TRUE === $_config['get_mtime'] ) ?
                            filemtime( "$dir/$currentItem" ) : NULL;
                            
                    // Read filesize?
                    $this->files[$counter]['size'] = 
                        ( TRUE === $_config['get_filesize'] ) ?
                            filesize( "$dir/$currentItem" ) : NULL;                     
                    
                    // Get total size of files in dir?
                    if ( TRUE === $_config['total_files_size'] )                            
                        $this->_directorySize += ( TRUE === $_config['get_filesize'] ) ? 
                            $this->files[$counter]['size'] : filesize( "$dir/$currentItem" );
                    
                                    
                } elseif ( is_dir( "$dir/$currentItem" ) ) {                
                    $this->files[$counter]['type'] = self::DR_DIRECTORY;
                } else {    
                    continue;
                }

                $counter++;                                         
            }
            
            closedir( $d );
        
        } else {                                
            throw new Exception( 'Can\'t find '.$dir );
        }
        
    }
    

    /**
     * Find parent directory
     * @param string directory
     * @return string
     */     
    public function findParentDirectory( $dir )
    {
        $parent_dir = dirname( $dir );
        
        if ( $dir === '/' || $dir === '\\' || ( ! is_dir( $parent_dir ) ) ) {
            return NULL;
        }
        
        return $parent_dir;
    }
    
    /**
     * Originally from http://php.net/manual/en/function.fileperms.php
     * Read file permissions
     * @param string file
     * @param boolean octal or human-readable? [optional]
     * @return string
     */     
    public function getFilePermissions($file,$octal=FALSE)
    {
        if ( TRUE === $octal ) {
            
            return (string)substr(sprintf('%o', fileperms($file)), -4);
        
        } else {
            
            $perms = fileperms($file);
            
            if (($perms & 0xC000) == 0xC000) {
                // socket
                $info = 's';
            } elseif (($perms & 0xA000) == 0xA000) {
                // symbolic link
                $info = 'l';
            } elseif (($perms & 0x8000) == 0x8000) {
                //regular file
                $info = '-';
            } elseif (($perms & 0x6000) == 0x6000) {
                //special block
                $info = 'b';
            } elseif (($perms & 0x4000) == 0x4000) {
                // directory
                $info = 'd';
            } elseif (($perms & 0x2000) == 0x2000) {
                // special symbol
                $info = 'c';
            } elseif (($perms & 0x1000) == 0x1000) {
                // FIFO
                $info = 'p';
            } else {
                // unknown
                $info = 'u';
            }

            // owner
            $info .= (($perms & 0x0100) ? 'r' : '-');
            $info .= (($perms & 0x0080) ? 'w' : '-');
            $info .= (($perms & 0x0040) ?
                        (($perms & 0x0800) ? 's' : 'x' ) :
                        (($perms & 0x0800) ? 'S' : '-'));

            // group
            $info .= (($perms & 0x0020) ? 'r' : '-');
            $info .= (($perms & 0x0010) ? 'w' : '-');
            $info .= (($perms & 0x0008) ?
                        (($perms & 0x0400) ? 's' : 'x' ) :
                        (($perms & 0x0400) ? 'S' : '-'));

            // other
            $info .= (($perms & 0x0004) ? 'r' : '-');
            $info .= (($perms & 0x0002) ? 'w' : '-');
            $info .= (($perms & 0x0001) ?
                        (($perms & 0x0200) ? 't' : 'x' ) :
                        (($perms & 0x0200) ? 'T' : '-'));
            
            return $info;
        }
    }
    
    /**
     * Get owner of file
     * @param string file
     * @param boolean return human readable names? [optional]
     */
    public function getFileOwner( $file, $human_readable=TRUE )
    {
        
        $file_owner['owner'] = fileowner ( $file );
        $file_owner['group'] =  filegroup ( $file );
        
        if ( FALSE === $owner || FALSE === $group ) {
            return NULL;
        }
                        
        if ( TRUE === $human_readable 
            && function_exists( 'posix_getpwuid' ) 
            && function_exists( 'posix_getgrgid') ) {
            
            $posix_name = posix_getpwuid( $file_owner['owner'] );
            $posix_group = posix_getgrgid( $file_owner['group'] );
            
            if ( NULL !== $posix_name && NULL !== $posix_group ) {
                
                $file_owner['owner'] = $posix_name['name'];
                $file_owner['group'] = $posix_group['name'];
            }
                            
        }
        
        return $file_owner;
        
    }
    
    /**
     * Return current directory
     * @return string
     */ 
    public function getCurrentDirectory()
    {
        return $this->_currentDirectory;
    }
    
    /**
     * Return parent directory
     * @return string
     */
    public function getParentDirectory()
    {
        return $this->_parentDirectory;
    }
    
    /**
     * Return total size in current directory
     * @return integer
     */
    public function getDirectorySize()
    {
        return $this->_directorySize;
    }
    
    /**
     * Return items count
     * @param integer calculate DR_FILE or DR_DIRECTORY [optional]
     * @return integer
     */
    public function DirectoryItemsCount( $onlyDR=NULL )
    {
        if ( NULL === $this->files ) {
            return NULL;
        }
        
        if ( NULL === $onlyDR ) {   
            return count( $this->files );
        } else {
            $counter = 0;   
            foreach ( $this->files as $file ) {                 
                if ( $file['type'] == $onlyDR ) {
                    $counter++;
                }                   
            }       
            return $counter;                
        }           
    }
    
    /**
     * Get default config values
     * @return array
     */
    private function getDefaultConfig()
    {
        return array(   
            'get_permissions'       => TRUE,
            'get_owner'             => TRUE,
            'store_full_path'       => TRUE,
            'get_mtime'             => TRUE,
            'get_filesize'          => TRUE,
            'get_octal_permissions' => FALSE,
            'total_files_size'      => TRUE,
            'exclude_names'         => array('/^[.]$/', // .
                                             '/^[.][.]$/', // ..
                                             '/^[.](.*?)$/' // .FILE
                                       ),
            
               );
    }
    
    /**
     * Compare file-name with pattern
     * @param string filename
     * @param array regular expression patterns
     * @return boolean
     */
    private function checkName( $name, $patterns )
    {
        foreach ( $patterns as $pattern ) {
            if ( preg_match ( $pattern, $name, $matches ) ) {
                return TRUE;
            }
        }
        
        return FALSE;
    }
    
    /**
     * Find items by pattern
     * @param string pattern
     * @param integer search only for DR_FILE or DR_DIRECTORY ? [optional]
     * @return integer items count
     */
    public function findItems( $pattern, $onlyDR=NULL )
    {
        if ( NULL === $this->files ) {
            return NULL;
        }
        
        $items = NULL;
        $count = 0;
        
        foreach ( $this->files as $file) {
            if ( preg_match ( $pattern, $file['name'], $matches) ) {
                
                if ( NULL === $onlyDR ) { // search all
                    $items[] = $file;
                    $count++;
                
                } elseif ( self::DR_FILE === $onlyDR 
                         && self::DR_FILE === $file['type'] ) { // search only files 
                    $items[] = $file;
                    $count++;
                
                } elseif ( self::DR_DIRECTORY === $onlyDR 
                         && self::DR_DIRECTORY === $file['type'] ) { // search only directorys
                    $items[] = $file;
                    $count++;       
                }
                
            }
        }
        
        $this->files = $items;
        return $count;
    }

    
}
// Class Filename:    DirectoryReader.php


